package tinf.entropijsko;

import tinf.ispis.IspisUDatoteku;
import java.util.ArrayList;

/**
 * Klasa implementira LZ77 koder i dekoder. Ima definirane maksimalne veličine
 * posmačnog prozora i prozora za kodiranje - 6 i 5.
 * 
 * @author Ana
 * */

public class LZ77 {

	private int posmacniMax = 6;
	private int kodiranjeMax = 5;
	private int posmacniProzor, prozorZaKodiranje;
	private String dekodiraniNiz;

	public String getDekodiraniNiz() {
		return dekodiraniNiz;
	}

	/**
	 * Stvara novi LZ77 (de)koder s definiranom veličinom posmačnog prozora i
	 * prozora za kodiranje. Ako je bilo koja od te dvije veličine veća od
	 * definiranog maksimuma, postavlja se na maksimum te se korisniku ispisuje
	 * poruka o mogućoj pogrešci.
	 * 
	 * @param posmacniProzor
	 *            duljina posmačnog prozora
	 * @param prozorZaKodiranje
	 *            duljina prozora za kodiranje
	 * */

	public LZ77(int posmacniProzor, int prozorZaKodiranje) {
		if (posmacniProzor > posmacniMax) {
			this.posmacniProzor = posmacniMax;
			System.out.println("Odabran je posmacni prozor duljine "
					+ posmacniProzor + "."
					+ "Skaliram na maksimalnu duljinu posmacnog prozora: "
					+ posmacniMax);
		} else
			this.posmacniProzor = posmacniProzor;

		if (prozorZaKodiranje > posmacniMax) {
			this.prozorZaKodiranje = kodiranjeMax;
			System.out.println("Odabran je prozor za kodiranje duljine "
					+ prozorZaKodiranje + "."
					+ "Skaliram na maksimalnu duljinu posmacnog prozora: "
					+ kodiranjeMax);
		} else
			this.prozorZaKodiranje = prozorZaKodiranje;
	}

	/**
	 * Kodira predani string u uređene trojke koristeći LZ77 algoritam.
	 * 
	 * @param nizZnakova
	 *            String koji se kodira
	 * */

	public ArrayList<UredjenaTrojka> kodirajNiz(String nizZnakova) {

		ArrayList<UredjenaTrojka> trojke = new ArrayList<UredjenaTrojka>();
		int krajPosmacnog = 0; // kraj posmačnog i početak prozora za kodiranje
		int pocetakPosmacnog, krajKodnog;
		String posmacni, kodni;
		int indexPronadjenog;

		IspisUDatoteku.upisiUDatoteku("LZ77 kodiranje");
		IspisUDatoteku.upisiUDatoteku("--------------");

		while (true) {

			pocetakPosmacnog = (krajPosmacnog - posmacniProzor) < 0 ? 0
					: (krajPosmacnog - posmacniProzor);
			krajKodnog = (krajPosmacnog + prozorZaKodiranje) > (nizZnakova
					.length()) ? (nizZnakova.length())
					: (krajPosmacnog + prozorZaKodiranje);
			int pomak, duljina;
			Character sljedeciSimbol;

			if (krajPosmacnog == nizZnakova.length()) {
				break;
			}

			// posmačni prozor kao String
			posmacni = nizZnakova.substring(pocetakPosmacnog, krajPosmacnog);

			String ispis = "(posmicni) - " + posmacni + ":";

			// prozor za kodiranje kao String
			kodni = nizZnakova.substring(krajPosmacnog, krajKodnog);
			ispis += kodni + " - (kodni)";

			boolean contains = false;

			for (int i = 1; i < kodni.length(); i++) {

				/*
				 * u slučaju da se nakon prepoznatog podniza u prozoru za
				 * kodiranje nalazi dio ili cijeli takav podniz (ili više njih)
				 * računa se dodatak duljini, na početku svake iteracije se
				 * postavlja na nula
				 */
				int dodatakDuljini = 0;

				// podniz kojeg tražimo u posmačnom prozoru
				String podniz = kodni.substring(0, kodni.length() - i);

				if (posmacni.contains(podniz)) {
					contains = true;
					// uvijek se uzima index zadnjeg pronađenog
					indexPronadjenog = posmacni.lastIndexOf(podniz);

					/*
					 * za slučaj da je podniz pronađen na samom kraju posmačnog
					 * prozora, provjerava se nalazi li se iza podniza u prozoru
					 * za kodiranje dio ili cijeli takav niz koji se onda može u
					 * jednom koraku iskodirati
					 */
					if (posmacni.endsWith(podniz)) {

						// dio prozora za kodiranje bez pronađenog podniza
						String drugiDioKodnog = kodni.substring(
								podniz.length(), kodni.length() - 1);

						while (true) {
							boolean kraj = false;

							for (int brojacPodniza = 0; brojacPodniza < podniz
									.length(); brojacPodniza++) {
								String zaProvjeriti = podniz.substring(0,
										podniz.length() - brojacPodniza);
								if (drugiDioKodnog.startsWith(zaProvjeriti)) {
									// povećava se dodatak duljini za dio
									// prepoznatog niza
									dodatakDuljini += zaProvjeriti.length();

									/*
									 * ako nakon prepoznatog podniza slijedi
									 * čitav podniz, onda se osvježava varijabla
									 * zaProvjeriti tako da se opet izuzme taj
									 * dio niza, te se ponovno vrše provjere u
									 * ostatku niza
									 */
									if (zaProvjeriti.length() == podniz
											.length()) {
										drugiDioKodnog = drugiDioKodnog
												.substring(podniz.length());
										break;
									}
									/*
									 * ako nismo pronašli ponovno čitav podniz,
									 * to je kraj provjere
									 */
									else {
										kraj = true;
									}
									break;
								} else {
									kraj = true;
								}
								/*
								 * ako dođemo do provjere za samo jedno slovo i
								 * niti ono ne odgovara, završili smo s
								 * provjerom
								 */
								if ((brojacPodniza == podniz.length() - 1)
										&& !drugiDioKodnog
												.contains(zaProvjeriti)) {
									kraj = true;
								}

							}
							if (kraj)
								break;
						}
					}

					pomak = krajPosmacnog
							- (indexPronadjenog + pocetakPosmacnog);
					duljina = podniz.length() + dodatakDuljini;
					sljedeciSimbol = nizZnakova.charAt(krajPosmacnog + duljina);
					trojke.add(new UredjenaTrojka(pomak, duljina,
							sljedeciSimbol));
					UredjenaTrojka nova = new UredjenaTrojka(pomak, duljina,
							sljedeciSimbol);
					if (nova != null) {
						IspisUDatoteku.upisiUDatoteku(ispis + "\t\t" + nova);
					}
					krajPosmacnog += duljina + 1;
					break;
				}

			}

			if (!contains) {
				trojke.add(new UredjenaTrojka(0, 0, nizZnakova
						.charAt(krajPosmacnog)));
				IspisUDatoteku.upisiUDatoteku(ispis
						+ "\t\t"
						+ new UredjenaTrojka(0, 0, nizZnakova
								.charAt(krajPosmacnog)));
				krajPosmacnog += 1;
			}

		}

		IspisUDatoteku
				.upisiUDatoteku("========================================================================================================================\n");

		return trojke;
	}

	/**
	 * Dekodira predanu listu uređenih trojki u niz simbola (String) koristeći
	 * LZ77 algoritam.
	 * 
	 * @param trojke
	 *            Lista uređenih trojki
	 * */
	public String dekodirajNiz(ArrayList<UredjenaTrojka> trojke)
			throws IllegalArgumentException {

		String niz = new String();
		String dodatak = new String();
		int duljina, pomak;
		boolean kraj = false;
		Character sljedeciSimbol;

		IspisUDatoteku.upisiUDatoteku("LZ77 dekodiranje");
		IspisUDatoteku.upisiUDatoteku("----------------");
		IspisUDatoteku.upisiUDatoteku("trojka\tdosad dekodirani niz");

		for (int trojka = 0; trojka < trojke.size(); trojka++) {
			UredjenaTrojka ut = trojke.get(trojka);
			String ispis = ut + "\t";
			duljina = ut.getDuljina();
			pomak = ut.getPomak();
			sljedeciSimbol = ut.getSljedeciSimbol();

			// ako prepoznamo kraj niza, treba zaustaviti dekodiranje
			if (sljedeciSimbol == '*') {
				kraj = true;
			}

			if (niz.length() - pomak < 0) {
				IspisUDatoteku
						.upisiUDatoteku(ispis
								+ "[Pomak ne moze biti veci od duljine niza u prozoru za kodiranje!]");
				IspisUDatoteku
						.upisiUDatoteku("========================================================================================================================\n");
				dekodiraniNiz = niz;
				throw new IllegalArgumentException(
						"Pomak ne moze biti veci od duljine niza u prozoru za kodiranje!"
								+ ut);
			}

			if (duljina == 0) {
				niz += sljedeciSimbol.toString();
			} else if (duljina <= pomak) {
				dodatak = niz.substring(niz.length() - pomak, niz.length()
						- pomak + duljina);
				niz += dodatak;
				niz += sljedeciSimbol.toString();
			} else if (pomak != 0) {
				for (int i = 0; i < duljina / pomak; i++) {
					dodatak = niz.substring(niz.length() - pomak);
					niz += dodatak;
				}
				int ostatak = duljina % pomak;
				niz += dodatak.substring(0, ostatak);
				niz += sljedeciSimbol.toString();
			} else {
				IspisUDatoteku
						.upisiUDatoteku(ispis
								+ "[Pomak ne smije biti 0 ako je duljina pronadenog niza razlicita od 0!]");
				IspisUDatoteku
						.upisiUDatoteku("========================================================================================================================\n");
				dekodiraniNiz = niz;
				throw new IllegalArgumentException(
						"Pomak ne smije biti 0 ako je duljina pronadenog niza razlicita od 0!"
								+ ut);
			}
			IspisUDatoteku.upisiUDatoteku(ispis + niz);

			// ako je prepoznat kraj niza, zaustavljamo dekodiranje
			if (kraj) {
				break;
			}
		}
		IspisUDatoteku
				.upisiUDatoteku("========================================================================================================================\n");

		return niz;
	}

	public int getPosmacniProzor() {
		return posmacniProzor;
	}

	public int getProzorZaKodiranje() {
		return prozorZaKodiranje;
	}

}
